package com.lam.framework.operLog.handlers;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimerTask;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.ModelAndView;

import com.lam.common.base.domain.Result;
import com.lam.common.base.page.TableData;
import com.lam.common.utils.CollectionUtil;
import com.lam.common.utils.DateUtil;
import com.lam.common.utils.IpUtils;
import com.lam.common.utils.JsonUtil;
import com.lam.common.utils.ServletUtils;
import com.lam.common.utils.SpringUtils;
import com.lam.common.utils.StringUtils;
import com.lam.framework.async.AsyncManager;
import com.lam.framework.operLog.annotation.Log;
import com.lam.framework.operLog.constant.LogConstant;
import com.lam.framework.operLog.enums.BusinessStatus;
import com.lam.framework.operLog.enums.BusinessType;
import com.lam.framework.operLog.enums.LogSave;
import com.lam.framework.operLog.enums.OperatorType;
import com.lam.framework.operLog.model.OperateLog;
import com.lam.framework.operLog.service.OperLogService;
import com.lam.framework.security.GlobalExceptionHandler;

import eu.bitwalker.useragentutils.UserAgent;

public class LogHandler {
	
	private static final Logger logger = LoggerFactory.getLogger(LogHandler.class);
	
	/** 参数日志格式 */
	private static final String REQ_PARAM_FM = "[param]:%s";
	/** [url,method],[title],[oper],[content]  */
	private static final String CONTENT_FM = "[%s %s][%s-%s]%s";
	/** [url,method,ip],[title],[oper],[content]  */
	private static final String CONTENT_IP_FM = "[%s %s, ip:%s][%s-%s]%s";
	
	private static final String REQUEST_FM = "request"+CONTENT_FM;
	private static final String REQUEST_IP_FM = "request"+CONTENT_IP_FM;
	private static final String RESPONSE_FM = "response"+CONTENT_IP_FM;
	private static final String RESPONSE_IP_FM = "response"+CONTENT_IP_FM;
	
	private static final String RES_RESULT_FM = "[result]:%s";
	private static final String EL_TIME_FM = "[cost:%sms]";
	
	private static Set<BusinessType> autoLogBusinessTypes = null;
	
	private static final Map<Class<?>, Logger> loggerMap = new LinkedHashMap<>();
	
	private static final String PARAM_SEPARATOR = ", ";
	
	private static Logger getLogger(Class<?> clazz) {
		Logger log = loggerMap.get(clazz);
		if(log == null) {
			log = LoggerFactory.getLogger(clazz);
			if(loggerMap.size() > 30) {
				loggerMap.remove(loggerMap.keySet().iterator().next());
			}
			loggerMap.put(clazz, log);
		}
		return log;		
	}
	
	public static void printReqParam(OperateLog operLog, Class<?> handlerClass){
		if(operLog == null){
			return ;
		}
		String param = operLog.printReqData()?String.format(REQ_PARAM_FM, operLog.getReqParam()):"";
		String logInfo;
		if(operLog.printIp()){
			logInfo = String.format(REQUEST_IP_FM, operLog.getReqMethod(), operLog.getReqUrl(), operLog.getReqIp(), operLog.getTitle(), operLog.getOper(), param);
		}else{
			logInfo = String.format(REQUEST_FM, operLog.getReqMethod(), operLog.getReqUrl(), operLog.getTitle(), operLog.getOper(), param);
		}
		getLogger(handlerClass).info(logInfo);
	}
	
	public static void printResResult(OperateLog operLog, Class<?> handlerClass){
		if(operLog == null){
			return ;
		}
		String elTime = operLog.elapsedTime()?String.format(EL_TIME_FM, operLog.getElTime()):"";
		String result = elTime + (operLog.printResData()?String.format(RES_RESULT_FM, operLog.getResResult()):"");
		String logInfo;
		if(operLog.printIp()){
			logInfo = String.format(RESPONSE_IP_FM, operLog.getReqMethod(), operLog.getReqUrl(), operLog.getReqIp(), operLog.getTitle(), operLog.getOper(), result);
		}else{
			logInfo = String.format(RESPONSE_FM, operLog.getReqMethod(), operLog.getReqUrl(), operLog.getTitle(), operLog.getOper(), result);
		}
		getLogger(handlerClass).info(logInfo);
	}
	
	public static void submitLog(OperateLog operLog){
		if(operLog == null){
			return ;
		}
		if(!isSaveLog(operLog)){
			return;
		}
		if(!operLog.saveReqData()){
			operLog.setReqParam(null);
		}
		if(!operLog.saveResData()){
			operLog.setResResult(null);
		}

		OperLogService service = SpringUtils.getBean(OperLogService.class);
		if(service == null) {
			return;
		}
		//异步保存数据库
		AsyncManager.execute(new TimerTask() {
			@Override
			public void run() {
				service.saveLog(operLog);
			}
		});
	}
	
	private static boolean isSaveLog(OperateLog operLog){
		if(operLog.getLogSave() == LogSave.NO){
			return false;
		}
		if(operLog.getLogSave() == LogSave.YES){
			return true;
		}
		return checkBusinessType(operLog.getBusinessType()); 
	}
	
	private static boolean checkBusinessType(String businessType) {
		BusinessType type;
		if(autoLogBusinessTypes == null) {
			autoLogBusinessTypes = new HashSet<>();
			String[] ss = LogConstant.AUTO_LOG_BUSINESS_TYPE.split(",");
			for (String s : ss) {
				type = BusinessType.valueFrom(s);
				if(type != null) {
					autoLogBusinessTypes.add(type);
				}
			}
		}
		type = BusinessType.valueFrom(businessType);
		return autoLogBusinessTypes.contains(type);
	}
	
	/**
	 * 收集请求结果数据log
	 * @param operLog
	 * @param responseResult
	 * @param e
	 * @return
	 */
	public static void collectResLog(OperateLog operLog, Object responseResult, Throwable e) {
		if(operLog == null || (!operLog.saveResData() && !operLog.printResData())){
			return ;
		}
		
		BusinessStatus businessStatus;
    	if(e == null) {
    		businessStatus = BusinessStatus.SUCCESS;
    	}else {
    		businessStatus = GlobalExceptionHandler.isFailException(e)? BusinessStatus.FAIL : BusinessStatus.EXCEPTION;
    	}
    	
		try {
			operLog.setReqStatus(businessStatus.getCode());
			if(responseResult instanceof Result) {
				Result result = (Result)responseResult;
				operLog.setReqDesc(result.getMsg());
				if(businessStatus == BusinessStatus.SUCCESS && !result.isSuccess()) {
					operLog.setReqStatus(BusinessStatus.FAIL.getCode());
				}
				responseResult = createPrintResResult(result);
			}
			operLog.setResResult(JsonUtil.toJsonString(responseResult, false, false));
			if (e != null) {
				operLog.setReqDesc(e.getMessage());
			}
		} catch (Exception exp) {
			logger.error("记录操作日志异常:" + exp.getMessage(), exp);
			operLog.setReqStatus(BusinessStatus.EXCEPTION.getCode());
			operLog.setReqDesc(exp.getMessage());
		}
	}
	
	private static Object createPrintResResult(Result result) {
		Object resData = result.getData();
		if(resData != null && (resData instanceof Collection || resData instanceof TableData) ) {
			Result newResult = new Result().code(result.getCode()).msg(result.getMsg());
			if(resData instanceof Collection) {
				newResult.setData(String.format("List Data size is (%s)!", ((Collection<?>)resData).size()));
				return newResult;
			}
			
			if(resData instanceof TableData) {
				TableData tableData = (TableData)resData;
				List<?> dataList = tableData.getRecords();
				tableData.setRecords(null);
				Map<String, Object> resDataMap = JsonUtil.parseJSON(tableData);
				tableData.setRecords(dataList);
				resDataMap.put("records", String.format("List Data size is (%s)!", dataList.size()));
				newResult.setData(resDataMap);
				return newResult;
			}
		}
		return resData;
	}
	
	/**
	 * 收集请求数据log
	 * @param handlerClass
	 * @param method
	 * @param args
	 * @return
	 */
	public static OperateLog collectReqLog(Class<?> handlerClass, Method method, Object[] args){
		try {
			HttpServletRequest request = ServletUtils.getRequest();
			// 获取注解
//			Log clazzLog = AnnotationUtils.findAnnotation(joinPoint.getTarget().getClass(), Log.class);
//			Log methodLog = AnnotationUtils.findAnnotation(((MethodSignature) joinPoint.getSignature()).getMethod(), Log.class);
			Log clazzLog = AnnotationUtils.findAnnotation(handlerClass, Log.class);
			Log methodLog = AnnotationUtils.findAnnotation(method, Log.class);
			if (methodLog == null || (!methodLog.saveReqData() && !methodLog.saveResData() && !methodLog.printReqData()
					&& !methodLog.printResData())) {
				return null;
			}
	
			OperateLog operLog = new OperateLog();
			//模块
			operLog.setTitle(StringUtils.isBlank(methodLog.title()) && clazzLog != null?clazzLog.title():methodLog.title());
			//功能描述 
			operLog.setOper(StringUtils.isBlank(methodLog.oper()) ? methodLog.businessType().getBusinessName() : methodLog.oper());		
			// 设置业务操作类型 
			operLog.setBusinessType(methodLog.businessType().getType());
			// 设置操作人类别
			OperatorType operatorType = AuthInfoUtil.getOperatorType(methodLog.operatorType());
			operLog.setOperatorType(operatorType.getType());
			operLog.setLogSave(methodLog.logSave());
			operLog.setSaveReqData(methodLog.saveReqData()?1:0);
			operLog.setSaveResData(methodLog.saveResData()?1:0);
			operLog.setPrintReqData(methodLog.printReqData()?1:0);
			operLog.setPrintResData(methodLog.printResData()?1:0);
			operLog.setElapsedTime(methodLog.elapsedTime()?1:0);
			operLog.setPrintIp(methodLog.printIp()?1:0);
			// 处理方法名称
			operLog.setHandler(handlerClass.getName() + "." + method.getName() + "()");
			// 设置请求方式
			operLog.setReqMethod(request.getMethod());
			// 请求的地址
			operLog.setReqUrl(request.getRequestURI());
			// 请求的ip
			operLog.setReqIp(IpUtils.getIpAddr(request));
			
			setRequestParam(methodLog.businessType(), args, operLog);
			collectClientInfo(request, operLog);
			
			// 获取当前的用户
			operLog.setOperName(AuthInfoUtil.getOperName(operatorType, methodLog.businessType(), args));
			operLog.setOperTime(DateUtil.getCurTime());
			operLog.setRemark("");
			
			return operLog;
		} catch (Exception exp) {
			logger.error("记录操作日志异常:" + exp.getMessage(), exp);
			return null;
		}
	}
	
	public static void collectClientInfo(HttpServletRequest request, OperateLog operLog) {
		try {
			UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
			// 获取客户端操作系统
			operLog.setOs(userAgent.getOperatingSystem().getName());
			// 获取客户端浏览器
			operLog.setBrowser(userAgent.getBrowser().getName());
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
	}
	
	/**
	 * 获取请求的参数，放到log中
	 * @param joinPoint
	 * @param operLog
	 */
	private static void setRequestParam(BusinessType businessType, Object[] args, OperateLog operLog) {
		if(!operLog.saveReqData() && !operLog.printReqData()){
			return;
		}
		if (isReadImputSteam(businessType, operLog.getReqMethod())) {
//			operLog.setReqParam(convert2JsonStr(ServletUtils.getParamterBodyString()));
			operLog.setReqParam(ServletUtils.getParamterBodyString());
		}else{
			Map<?, ?> paramsMap = (Map<?, ?>) ServletUtils.getRequest().getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
			if(CollectionUtil.isEmpty(paramsMap)){
				paramsMap = ServletUtils.getParamter();
			}
			if(CollectionUtil.isEmpty(paramsMap)){
				paramsMap = ServletUtils.getFormParamter();
			}
			operLog.setReqParam(JsonUtil.toJsonString(paramsMap, false, false));
		}
		operLog.setOperParam(argsArrayToString(args));
	}
	
	private static boolean isReadImputSteam(BusinessType businessType, String reqMethod) {
		return ("put".equalsIgnoreCase(reqMethod) || "post".equalsIgnoreCase(reqMethod)) 
				&& businessType != BusinessType.UPLOAD && businessType != BusinessType.IMPORT ;
	}
	
//	private static String convert2JsonStr(String str) {
//		try {
//			return JsonUtil.toJsonString(JsonUtil.parseJSON(str), false, false);
//		} catch (Exception e) {
//			return str;
//		}
//	}

	/** 参数拼装 */
	private static String argsArrayToString(Object[] paramsArray) {
		if(paramsArray == null || paramsArray.length == 0){
			return StringUtils.EMPTY;
		}
		StringBuilder params = new StringBuilder();
		int c = 0;
		for (int i = 0; i < paramsArray.length; i++) {
			if (!isFilterObject(paramsArray[i])) {
				if(!isFileObject(paramsArray[i], params)) {
					params.append(JsonUtil.toJsonString(paramsArray[i], false, false)).append(PARAM_SEPARATOR);
				}
				c++;
			}
		}
		if(c==0){
			return "";
		}
		params.deleteCharAt(params.length()-1).deleteCharAt(params.length()-1);
		if(c>1){
			return "["+params.append("]").toString().trim();
		}
		return params.toString().trim();
	}

	/**
	 * 判断是否需要过滤的对象
	 * 
	 * @param o 对象信息
	 * @return 如果是需要过滤的对象，则true, 否则false
	 */
	public static boolean isFilterObject(Object o) {
		return o instanceof HttpServletRequest || o instanceof HttpServletResponse || o instanceof WebDataBinder 
				|| o instanceof ModelAndView || o instanceof Model || o instanceof OperateLog;
	}
	
	/**
	 * 是否web文件对象
	 * @param o
	 * @return
	 */
	public static boolean isFileObject(Object o, StringBuilder params) {
		
		if(o instanceof MultipartFile) {
			params.append(((MultipartFile)o).getOriginalFilename()).append(PARAM_SEPARATOR);
			return true;
		}
		
		if(o instanceof MultipartFile[]) {
			params.append("[");
			MultipartFile[] arr = (MultipartFile[])o;
			for (MultipartFile file : arr) {
				params.append(file.getOriginalFilename()).append(PARAM_SEPARATOR);
			}
			if(arr.length > 0) {
				params.deleteCharAt(params.length()-1).deleteCharAt(params.length()-1);
			}
			params.append("]").append(PARAM_SEPARATOR);
			return true;
		}
		
		if(o instanceof Collection) {
			List<?> list = (List<?>)o;
			if(CollectionUtil.isEmpty(list) || !(list.get(0) instanceof MultipartFile)) {
				return false;
			}
			params.append("[");
			for (Object object : list) {
				params.append(((MultipartFile)object).getOriginalFilename()).append(PARAM_SEPARATOR);
			}
			if(list.size() > 0) {
				params.deleteCharAt(params.length()-1).deleteCharAt(params.length()-1);
			}
			params.append("]").append(PARAM_SEPARATOR);
		}
		
		return false;
	}
	
	
}
